﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Formula;
using WMM.Tapioca.Formula.Filters;
using Saxon.Api;
using WMM.Tapioca.Instance;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using WMM.Tapioca.Utilities;
using WMM.Tapioca.XbrlDimension;
using System.Xml;
using System.Collections;
using System.IO;
using WMM.Tapioca.Formula.Functions;
using log4net.Appender;
using log4net.Core;
using WMM.Tapioca.Formula.Aspects;

namespace WMM.Tapioca.Validate
{
    /// <summary>
    /// Class that validates the formula linkbase.
    /// </summary>
    public class ValidateLinkbaseFormula
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private DTS dts;

        Dictionary<string, string> namespaces = new Dictionary<string, string>();


        /// <summary>
        /// The constructor.
        /// </summary>
        public ValidateLinkbaseFormula(DTS dtsXbrl, string message)
        {
            dts = dtsXbrl;            

            log.Info("\n\nFormula trace - " + message);
        }

        
        /// <summary>
        /// The constructor.
        /// </summary>
        public ValidateLinkbaseFormula(DTS dtsXbrl)
        {
            dts = dtsXbrl;            

            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                if (linkbase.NamespaceManager.HasNamespace("variable"))
                {
                    log.Info("Validating linkbases formula:\"" + linkbase.FileName + "\". ExtendedLink count:" + linkbase.ExtendedLinks.Count);
                    Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases formula:" + linkbase.FileName + " extendedLink count:" + linkbase.ExtendedLinks.Count));

                    ValidateFormulaElement(linkbase);
                }
            }
        }



        private void ValidateFormulaElement(LinkbaseDocument linkbase)
        {
            foreach (FormulaXBRL formula in dts.DtsFormula.Formulas)
            {
                ValidateFormulaAspectRules(formula);

                ValidateConflictingAspectRules(formula);

                ValidateAspectModel(formula);

                ValidateConflictingParameter(formula);

            }

            foreach (ConsistencyAssertion consistencyAssertion in dts.DtsFormula.ConsistencyAssertions)
            {
                foreach (FormulaXBRL formula in consistencyAssertion.FormulasXbrl)
                {
                    ValidateFormulaAspectRules(formula);

                    ValidateConflictingAspectRules(formula);

                    ValidateAspectModel(formula);

                    ValidateConflictingParameter(formula);
                }
            }

            foreach (ExistenceAssertion existenceAssertion in dts.DtsFormula.ExistenceAssertions)
            {
                
            }
        }

        private void ValidateConflictingParameter(FormulaXBRL formula)
        {
            foreach (Parameter parameter1 in formula.Parameters)
            {
                foreach (Parameter parameter2 in formula.Parameters)
                {
                    if (parameter1 != parameter2 && parameter1.Name == parameter2.Name)
                    {
                        log.Error("Formula error: The formula: " + formula.Label + " . Two parameters in the one discoverable taxonomy set have the same QName specified by their @name attributes.");
                        dts.ValidatedFormula = false;
                    }
                }
            }
        }


        private void ValidateFormulaAspectRules(FormulaXBRL formula)
        {
            if (formula.Source == null && formula.ConceptAspectFormula == null)
            {
                log.Error("Formula error: The formula: " + formula.Label + " is missing concept rule");
                dts.ValidatedFormula = false;
            }

            //if ((dts.ThisInstance == null && IsNumeric(formula)) && formula.Source == null && formula.UnitAspectFormula == null)
            if (formula.Source == null && formula.UnitAspectFormula == null)
            {
                log.Error("Formula error: The formula: " + formula.Label + " doesn't have a unit aspect rule.");
                dts.ValidatedFormula = false;
            }

            if (formula.Source == null && formula.EntityIdentifierAspectFormula == null)
            {
                log.Error("Formula error: The formula: " + formula.Label + " omits an aspect rule for the entity identifier aspect.");
                dts.ValidatedFormula = false;
            }

            if (formula.Source == null && formula.PeriodAspectFormula == null)
            {
                log.Error("Formula error: The formula: " + formula.Label + " is missing the period aspect rule.");
                dts.ValidatedFormula = false;
            }

            if (formula.Source == null && formula.ConceptAspectFormula != null && formula.ConceptAspectFormula.Source == null &&
               (formula.ConceptAspectFormula.Qname == null && formula.ConceptAspectFormula.QnameExpression == null))
            {
                log.Error("Formula error: The formula: " + formula.Label + " does not have a nearest source and the concept rule does not have a child element.");
                dts.ValidatedFormula = false;
            }

            if (formula.Source == null && formula.EntityIdentifierAspectFormula != null && formula.EntityIdentifierAspectFormula.Source == null &&
               (formula.EntityIdentifierAspectFormula.Scheme == null && formula.EntityIdentifierAspectFormula.ValueScheme != null))
            {
                log.Error("Formula error: The formula: " + formula.Label + " does not have the entity identifier with a nearest source and does not have a scheme attribute.");
                dts.ValidatedFormula = false;
            }

            if (formula.Source == null && formula.EntityIdentifierAspectFormula != null && formula.EntityIdentifierAspectFormula.Source == null &&
               (formula.EntityIdentifierAspectFormula.ValueScheme == null && formula.EntityIdentifierAspectFormula.Scheme != null))
            {
                log.Error("Formula error: The formula: " + formula.Label + " does not have the entity identifier with a nearest source and does not have a value attribute.");
                dts.ValidatedFormula = false;
            }

            if (formula.Source == null && formula.EntityIdentifierAspectFormula != null && formula.EntityIdentifierAspectFormula.Source == null &&
               (formula.EntityIdentifierAspectFormula.Scheme == null && formula.EntityIdentifierAspectFormula.ValueScheme == null))
            {
                log.Error("Formula error: The formula: " + formula.Label + " does not have the entity identifier with a nearest source does and not have either scheme or value attributes.");
                dts.ValidatedFormula = false;
            }

            if (formula.Source == null && formula.PeriodAspectFormula != null && formula.PeriodAspectFormula.Source == null &&
              (formula.PeriodAspectFormula.Forever == null && formula.PeriodAspectFormula.Instant == null && formula.PeriodAspectFormula.DurationStart == null))
            {
                log.Error("Formula error: The formula: " + formula.Label + " does not have a nearest source and the period rule does not have a child element.");
                dts.ValidatedFormula = false;
            }

            if (formula.Source == null && formula.UnitAspectFormula != null && formula.UnitAspectFormula.Source == null &&
              (formula.UnitAspectFormula.DivideByAspectFormulas.Count == 0 && formula.UnitAspectFormula.MultiplyByAspectFormulas.Count == 0))
            {
                log.Error("Formula error: The formula: " + formula.Label + " does not have a nearest source and the unit rule does not have a child element.");
                dts.ValidatedFormula = false;
            }

            foreach (ExplicitDimensionAspect explicitDimensionAspectFormula in formula.ExplicitDimensionAspectFormulas)
            {
                if (formula.Source == null && explicitDimensionAspectFormula.Source == null &&
                    explicitDimensionAspectFormula.Member == null)
                {
                    log.Error("Formula error: The formula: " + formula.Label + ". The explicit dimension rule does not have any child elements and does not have a SAV for the dimension that it identifies with its @dimension attribute.");
                    dts.ValidatedFormula = false;
                }

                Dimension dimension = GetDimensionByName(explicitDimensionAspectFormula.Dimension, "explicitDimension");

                if (dimension == null)
                {
                    log.Error("Formula error: The formula: " + formula.Label + ". Bad usage of explicit dimension.");
                    dts.ValidatedFormula = false;
                }
            }

            foreach (TypedDimensionAspect typedDimensionAspectFormula in formula.TypedDimensionAspectFormulas)
            {
                if (formula.Source == null && typedDimensionAspectFormula.Source == null &&
                    typedDimensionAspectFormula.Value == null)
                {
                    log.Error("Formula error: The formula: " + formula.Label + ". The typed dimension rule does not have any child elements and does not have a SAV for the dimension that it identifies with its @dimension attribute.");
                    dts.ValidatedFormula = false;
                }

                Dimension dimension = GetDimensionByName(typedDimensionAspectFormula.Dimension, "typedDimension");

                if (dimension == null)
                {
                    log.Error("Formula error: The formula: " + formula.Label + ". Bad usage of typed dimension.");
                    dts.ValidatedFormula = false;
                }
            }

            if (formula.Source != null && formula.Source.Equals("formula:uncovered") && !formula.ImplicitFiltering)
            {
                log.Error("Formula error: The formula: " + formula.Label + ". The source contains the uncovered QName but its formula does not use implicit filtering.");
                dts.ValidatedFormula = false;
            }

            if (formula.PeriodAspectFormula != null && formula.PeriodAspectFormula.Source != null)
            {
                FactVariable factVariable = null;

                foreach (VariableSet variableSet in formula.VariableSetArcs)
                {
                    if (formula.PeriodAspectFormula.Source.Equals(variableSet.Name))
                    {
                        factVariable = GetFactVariable(formula, variableSet);

                        if (factVariable.BindAsSequence)
                        {
                            log.Error("Formula error: The formula: " + formula.Label + ". The source contains the QName of a fact variable that binds as a sequence.");
                            dts.ValidatedFormula = false;
                        }

                        if (factVariable.FallbackString == null)
                        {
                            log.Error("Formula error: The formula: " + formula.Label + ". The formula's source contains the QName of a fact variable that can bind to an empty sequence.");
                            dts.ValidatedFormula = false;
                        }
                    }
                }
            }

            if (formula.Source != null)
            {
                FactVariable factVariable = null;

                foreach (VariableSet variableSet in formula.VariableSetArcs)
                {
                    if (formula.Source.Equals(variableSet.Name))
                    {
                        factVariable = GetFactVariable(formula, variableSet);
                        break;
                    }
                }

                if (factVariable == null && !formula.Source.Equals("formula:uncovered"))
                {
                    log.Error("Formula error: The formula: " + formula.Label + ". The source in a formula contains a QName that is not the uncovered QName or the QName of any one of the formula's fact variables.");
                    dts.ValidatedFormula = false;
                }
            }

        }



        private Dimension GetDimensionByName(string qname, string dimensionType)
        {
            string name = qname.Substring(qname.IndexOf(':') + 1);

            foreach (Dimension dimension in dts.DtsDimension.Dimensions)
            {
                if (dimension.Concept.Name.Equals(name) && dimension.DimensionType.Equals(dimensionType))
                {
                    return dimension;
                }
            }
            return null;
        }


        private void ValidateConflictingAspectRules(FormulaXBRL formula)
        {
            if (formula.PeriodAspectFormula != null && formula.ConceptAspectFormula != null)
            {
                if (formula.ConceptAspectFormula.Qname != null)
                {
                    string name = formula.ConceptAspectFormula.Qname.LocalName;
                    Concept concept = dts.GetConceptByName(name);

                    if (concept.PeriodType == Concept.EnumPeriodType.Duration && formula.PeriodAspectFormula.Instant != null ||
                        concept.PeriodType == Concept.EnumPeriodType.Instant && formula.PeriodAspectFormula.DurationStart != null)
                    {
                        log.Error("Formula error: The formula has conflicting aspect period rule and concept aspect rule");
                        dts.ValidatedFormula = false;
                    }

                    if (!concept.SchemaElement.ElementSchemaType.IsNumeric() && formula.UnitAspectFormula != null)
                    {
                        log.Error("Formula error: The formula has conflicting aspect unit rule and concept aspect rule");
                        dts.ValidatedFormula = false;
                    }
                }
            }
        }


        private void ValidateAspectModel(FormulaXBRL formula)
        {
            if (formula.AspectModel.Equals("non-dimensional") &&
                (formula.ExplicitDimensionAspectFormulas.Count > 0 || formula.TypedDimensionAspectFormulas.Count > 0))
            {
                log.Error("Formula error: The formula: " + formula.Label + " includes an aspect rule for an aspect that is not defined in the formula's aspect model.");
                dts.ValidatedFormula = false;
            }
        }


        private string GetLocation(FactItem item)
        {
            string location = "not-tuple";
            Concept conceptParent = dts.GetConceptByName(item.FactElement.Name.LocalName);

            if (conceptParent.SubstitutionGroup == Concept.EnumSubstitutionGroup.Tuple)
            {
                location = "tuple";
            }

            return location;
        }



        private Locator GetLocator(Arc arc, string label)
        {
            foreach (Locator locator in arc.ExtendedLinkParent.Locators)
            {
                if (label.Equals(locator.Label))
                {
                    return locator;
                }
            }

            return null;
        }



        private FactVariable GetFactVariable(VariableSet variableSet, VariableSet variableSetArc)
        {
            foreach (FactVariable factVariable in variableSet.FactVariables)
            {
                if (factVariable.Label.Equals(variableSetArc.To))
                {
                    return factVariable;
                }
            }

            return null;
        }

  
    }
}
